/**
 * Copyright (C) 2010 altuure <altuure [AT] gmail [DOT] com> http://www.altuure.com/projects/yagdao
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.altuure.yagdao.metadata.jpa;

import com.altuure.yagdao.jpa.EntityManagerAccessor;
import com.altuure.yagdao.metadata.MetadataHelper;
import com.altuure.yagdao.metadata.MetamodelPropertyDescriptor;
import com.altuure.yagdao.metadata.impl.MetamodelPropertyDescriptorImpl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.persistence.EntityManager;
import javax.persistence.metamodel.*;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.util.*;

/**
 * Copyright (C) 2010 altuure <altuure [AT] gmail [DOT] com> http://www.altuure.com/projects/yagdao
 * <p/>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Read meta mode from jpa info
 */

public class JPAMetadataHelperImpl implements MetadataHelper {
    private static final Log log = LogFactory.getLog(JPAMetadataHelperImpl.class);
    private final Object LOCK = new Object();
    private boolean initDone = false;
    private EntityManagerAccessor entityManagerAccessor;
    private Set<Class> entities;
    private Set<Class> embededTypes;
    private Map<Class, Map<String, MetamodelPropertyDescriptor>> foundmodel = null;

    public JPAMetadataHelperImpl() {
    }

    /**
     * read whole method for once
      * @throws IntrospectionException
     */
    public synchronized void init() throws IntrospectionException {
        if (initDone)
            return;
        Metamodel metamodel = entityManagerAccessor.getEntityManager().getMetamodel();
        Set<EntityType<?>> entityTypes = metamodel.getEntities();
        foundmodel = new LinkedHashMap<Class, Map<String, MetamodelPropertyDescriptor>>();
        entities = new HashSet<Class>();
        for (EntityType<?> entityType : entityTypes) {
            Class<?> entityClass = entityType.getJavaType();
            entities.add(entityClass);
            Map<String, MetamodelPropertyDescriptor> map = readEntityModel(metamodel, entityClass);
            if (log.isDebugEnabled()) {
                for (String s : map.keySet()) {
                    log.debug("\t" + map.get(s));
                }
            }
            foundmodel.put(entityClass, map);
        }

        initDone = true;
    }

    public Set<Class> getEntityClasses() throws IntrospectionException {
        init();
        return entities;
    }



    public Map<String, MetamodelPropertyDescriptor> getMetadata(Class aClass) throws IntrospectionException {
        init();
        return new LinkedHashMap<String, MetamodelPropertyDescriptor>(foundmodel.get(aClass));
    }

    private Map<String, MetamodelPropertyDescriptor> readEntityModel(Metamodel metamodel, Class aClass) throws IntrospectionException {
        if (log.isDebugEnabled())
            log.debug("reading model:" + aClass.getName());
        Map<String, MetamodelPropertyDescriptor> result = new LinkedHashMap<String, MetamodelPropertyDescriptor>();
        final EntityType entityType = metamodel.entity(aClass);
        final Set<Attribute> attributes = entityType.getAttributes();
        for (Attribute attribute : attributes) {
            String name = attribute.getName();

            boolean isCollection = attribute.isCollection();
            Class type = attribute.getJavaType();
            boolean isVersion = false;
            boolean isId = false;
            Class targetType = null;
            MetamodelPropertyDescriptor.MappingType mappingType = null;
            Bindable.BindableType bindableType=null;
           MetamodelPropertyDescriptor.BindType bindType = null;
            if (attribute instanceof SingularAttribute) {
                SingularAttribute singularAttribute = (SingularAttribute) attribute;
                isId = singularAttribute.isId();
                isVersion = singularAttribute.isVersion();
                bindType = getBindType(singularAttribute);

            } else if (attribute instanceof PluralAttribute) {
                PluralAttribute collectionAttribute = (PluralAttribute) attribute;
                Type elementType = collectionAttribute.getElementType();

                bindType = getBindType(collectionAttribute);
                targetType = elementType.getJavaType();
                switch (collectionAttribute.getCollectionType()) {

                    case COLLECTION:
                        mappingType = MetamodelPropertyDescriptor.MappingType.COLLECTION;
                        break;
                    case SET:
                        mappingType = MetamodelPropertyDescriptor.MappingType.SET;

                        break;
                    case LIST:
                        mappingType = MetamodelPropertyDescriptor.MappingType.LIST;

                        break;
                    case MAP:
                        mappingType = MetamodelPropertyDescriptor.MappingType.MAP;

                        break;
                }

            }
            MetamodelPropertyDescriptorImpl descriptor = new MetamodelPropertyDescriptorImpl(name, aClass);
            descriptor.setId(isId);
            descriptor.setCollection(isCollection);
            descriptor.setVersion(isVersion);
            descriptor.setType(type);
            descriptor.setTargetType(targetType);
            descriptor.setMappingType(mappingType);
            descriptor.setBindType(bindType);
            result.put(name, descriptor);


        }

        return result;
    }

    private MetamodelPropertyDescriptor.BindType getBindType( Attribute singularAttribute) {
        MetamodelPropertyDescriptor.BindType bindType=null;
        Type.PersistenceType bindableType;
        if (singularAttribute instanceof SingularAttribute) {
            SingularAttribute attribute = (SingularAttribute) singularAttribute;
            bindableType = attribute.getType().getPersistenceType();
        }else if (singularAttribute instanceof PluralAttribute) {
            PluralAttribute pluralAttribute = (PluralAttribute) singularAttribute;
             bindableType = pluralAttribute.getElementType().getPersistenceType();
        }else
        return null;
        
        switch (bindableType) {


            case ENTITY:
                  return MetamodelPropertyDescriptor.BindType.ENTITY;
            case EMBEDDABLE:
                  return MetamodelPropertyDescriptor.BindType.EMBEDDABLE;
            case MAPPED_SUPERCLASS:
                 return MetamodelPropertyDescriptor.BindType.MAPPED_SUPERCLASS;
            case BASIC:
                  return MetamodelPropertyDescriptor.BindType.BASIC;
        }
        return bindType;
    }

    public void setEntityManagerAccessor(EntityManagerAccessor entityManagerAccessor) {
        this.entityManagerAccessor = entityManagerAccessor;
    }
}
